/*
Copyright 2010 Iain Surgey.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

vec3 *RCE_clipvert;
vec3 *RCE_pv0;
vec3 *RCE_pv1;
int RCE_swap;

vec3	r_leftenter, r_leftexit;
vec3	r_rightenter, r_rightexit;

vec3 RCE_v0;
vec3 RCE_v1;
vec3 RCE_v2;

vec3		RRF_p_normal;

int				r_emitted;
float			r_nearzi;
float			r_u1, r_v1, r_lzi1;
int				r_ceilv1;

int	cacheoffset;
medge_t			*r_pedge;

vec3	REE_local, REE_transformed;

#__body__

private var r_leftclipped : Bool;
private var r_rightclipped : Bool;
private var r_lastvertvalid : Bool;
private var makeleftedge : Bool;
private var makerightedge : Bool;
private var r_nearzionly : Bool;

/*
================
R_RenderFace
================
*/
private function R_RenderFace (fa : Int, clipflags : Int)
{
	#{s| msurface_t *fa}
	#{s| int clipflags}
	#{d| int			i, lindex,mask,cachedofs}
	//unsigned	mask;
	#{d| mplane_t	*pplane}
	#{d| float		distinv}
	//#{vec3_t		p_normal;
	#{d| medge_t		*pedges}
	#{d| clipplane_t	*pclip, *cclip}
	#{d| mtexinfo_t *ti}
	
	// sky surfaces encountered in the world will cause the
	// environment box surfaces to be emited
	if ( (#{fa->flags} & SURF_DRAWSKY) != 0 )
	{
		R_EmitSkyBox ();
		return;
	}
	
	ti = #{fa->texinfo};
	
	// translucent surfaces are not drawn by the edge renderer
	if ( (#{ti->flags} & (SURF_TRANS33|SURF_TRANS66)) != 0 || #{ti->image} < 4)
	{
		return;
	}
	
// skip out if no more surfs
	if ((surface_p) >= surf_max)
	{
		r_outofsurfaces++;
		return;
	}
	
// ditto if not enough edges left, or switch to auxedges if possible
	if ((edge_p + (  (#{fa->numedges} + 4)*#{sizeof(edge_t)}  ) ) >= edge_max)
	{
		r_outofedges += #{fa->numedges};
		return;
	}
	
	c_faceclip++;
	
	pclip = -1;
	mask = 0x08;
	i = 3;
	while (i>=0)
	{
		if ((clipflags & mask) != 0)
		{
			#{view_clipplanes[i].next = pclip};
			#{pclip = &view_clipplanes[i]};
		}
		mask >>= 1;
		i--;
	}
	
// push the edges through
	r_emitted = 0;
	r_nearzi = 0;
	r_nearzionly = false;
	makeleftedge = makerightedge = false;
	pedges = #{currentmodel->edges};
	r_lastvertvalid = false;

	for (i in 0...#{fa->numedges})
	{
		
		lindex = #{currentmodel->surfedges[fa->firstedge + i]};

		if (lindex > 0)
		{
			r_pedge = #{&pedges[lindex]};
			cacheoffset = #{r_pedge->cachededgeoffset};
			
		// if the edge is cached, we can just reuse the edge
			
			if ((cacheoffset & FULLY_CLIPPED_CACHED) != 0)
			{
				
				if ((cacheoffset & FRAMECOUNT_MASK) == r_framecount)
				{
					r_lastvertvalid = false;
					continue;
				}
			}
			else
			{
				if (((edge_p - #{r_edges}) > cacheoffset) && 
					(Memory.getI32(#{r_edges}+cacheoffset) == r_pedge))
				{
					R_EmitCachedEdge ();
					r_lastvertvalid = false;
					continue;
				}
			}
			
		// assume it's cacheable
			cacheoffset = edge_p - #{r_edges};
			r_leftclipped = r_rightclipped = false;
			VectorCopy(#{&currentmodel->vertexes[r_pedge->v[0]]},RCE_pv0);
			VectorCopy(#{&currentmodel->vertexes[r_pedge->v[1]]},RCE_pv1);
			R_ClipEdge (pclip);
			#{r_pedge->cachededgeoffset = cacheoffset};

			if (r_leftclipped)
				makeleftedge = true;
			if (r_rightclipped)
				makerightedge = true;
			r_lastvertvalid = true;
		}
		else
		{
			lindex = -lindex;
			r_pedge = #{&pedges[lindex]};
			cacheoffset = #{r_pedge->cachededgeoffset};
			
		// if the edge is cached, we can just reuse the edge
			if ((cacheoffset & FULLY_CLIPPED_CACHED) != 0)
			{
				if ((cacheoffset & FRAMECOUNT_MASK) == r_framecount)
				{
					r_lastvertvalid = false;
					continue;
				}
			}
			else
			{
			// it's cached if the cached edge is valid and is owned
			// by this medge_t
				if (((edge_p - #{r_edges}) > cacheoffset) && 
					(Memory.getI32(#{r_edges}+cacheoffset) == r_pedge))
				{
					R_EmitCachedEdge ();
					r_lastvertvalid = false;
					continue;
				}
			}

		// assume it's cacheable
			cacheoffset = edge_p - #{r_edges};
			r_leftclipped = r_rightclipped = false;
			VectorCopy(#{&currentmodel->vertexes[r_pedge->v[1]]},RCE_pv0);
			VectorCopy(#{&currentmodel->vertexes[r_pedge->v[0]]},RCE_pv1);
			R_ClipEdge (pclip);
			#{r_pedge->cachededgeoffset = cacheoffset};

			if (r_leftclipped)
				makeleftedge = true;
			if (r_rightclipped)
				makerightedge = true;
			r_lastvertvalid = true;
		}
	}

// if there was a clip off the left edge, add that edge too
// FIXME: faster to do in screen space?
// FIXME: share clipped edges?
	if (makeleftedge)
	{
		r_pedge = -1; //#{&tedge};
		r_lastvertvalid = false;
		VectorCopy(#{r_leftexit.p},RCE_pv0);
		VectorCopy(#{r_leftenter.p},RCE_pv1);
		R_ClipEdge (#{pclip->next});
	}

// if there was a clip off the right edge, get the right r_nearzi
	if (makerightedge)
	{
		r_pedge = -1; //#{&tedge};
		r_lastvertvalid = false;
		r_nearzionly = true;
		VectorCopy(#{r_rightexit.p},RCE_pv0);
		VectorCopy(#{r_rightenter.p},RCE_pv1);
		R_ClipEdge (#{view_clipplanes[1].next});
	}

// if no edges made it out, return without posting the surface
	if (r_emitted < 1) {
		return;
	}
	
	r_polycount++;
	
	#{surface_p->msurf = fa};
	#{surface_p->nearzi = r_nearzi};
	#{surface_p->flags = fa->flags};
	#{surface_p->insubmodel = 0};
	#{surface_p->spanstate = 0};
	#{surface_p->entity = -1}; //currententity
	#{surface_p->key = r_currentkey};
	r_currentkey++;
	#{surface_p->spans = -1};
	
	#{pplane = fa->plane};
// FIXME: cache this?
	TransformVector (#{pplane->normal.p}, #{RRF_p_normal.p});
// FIXME: cache this?
	distinv = 1.0 / (#{pplane->dist} - DotProduct (#{r_origin}, #{pplane->normal.p}));
	
	#{surface_p->d_zistepu = RRF_p_normal.p[0] * xscaleinv * distinv};
	#{surface_p->d_zistepv = -RRF_p_normal.p[1] * yscaleinv * distinv};
	#{surface_p->d_ziorigin = RRF_p_normal.p[2] * distinv - xcenter * surface_p->d_zistepu - ycenter * surface_p->d_zistepv};
	
	surface_p+=#{sizeof(surf_t)};
}

/*
================
R_EmitCachedEdge
================
*/
private function R_EmitCachedEdge ()
{
	#{d| edge_t		*pedge_t}
	
	pedge_t = (#{r_edges + r_pedge->cachededgeoffset});

	if (#{pedge_t->surfs[0]} == 0)
		#{pedge_t->surfs[0] = #[Std.int(]#(surface_p - surfaces)/sizeof(surf_t))};
	else
		#{pedge_t->surfs[1] = #[Std.int(]#(surface_p - surfaces)/sizeof(surf_t))};

	if (#{pedge_t->nearzi} > r_nearzi)	// for mipmap finding
		r_nearzi = #{pedge_t->nearzi};

	r_emitted = 1;
}


/*
================
R_ClipEdge
================
*/
private function R_ClipEdge (clip : Int)
{
	#{d| float		d0, d1, f}
	#{s| clipplane_t *clip}
	
	if (clip > 4)
	{
		do
		{
			d0 = DotProduct (#{RCE_pv0->p}, #{clip->normal.p}) - #{clip->dist};
			d1 = DotProduct (#{RCE_pv1->p}, #{clip->normal.p}) - #{clip->dist};

			if (d0 >= 0)
			{
			// point 0 is unclipped
				if (d1 >= 0)
				{
				// both points are unclipped
					continue;
				}

			// only point 1 is clipped

			// we don't cache clipped edges
				cacheoffset = 0x7FFFFFFF;

				f = d0 / (d0 - d1);
				#{RCE_clipvert->p[0] = RCE_pv0->p[0] + f * (RCE_pv1->p[0] - RCE_pv0->p[0])};
				#{RCE_clipvert->p[1] = RCE_pv0->p[1] + f * (RCE_pv1->p[1] - RCE_pv0->p[1])};
				#{RCE_clipvert->p[2] = RCE_pv0->p[2] + f * (RCE_pv1->p[2] - RCE_pv0->p[2])};

				if (#{clip->leftedge} != 0)
				{
					r_leftclipped = true;
					VectorCopy(RCE_clipvert,#{r_leftexit.p});
				}
				else if (#{clip->rightedge} != 0)
				{
					r_rightclipped = true;
					VectorCopy(RCE_clipvert,#{r_rightexit.p});
				}
				
				RCE_swap = RCE_clipvert; // FIXME: use XOR swap
				RCE_clipvert = RCE_pv1;
				RCE_pv1 = RCE_swap;
				R_ClipEdge (#{clip->next});
				return;
			}
			else
			{
			// point 0 is clipped
				if (d1 < 0)
				{
				// both points are clipped
				// we do cache fully clipped edges
					if (!r_leftclipped)
						cacheoffset = FULLY_CLIPPED_CACHED |
								(r_framecount & FRAMECOUNT_MASK);
					return;
				}

			// only point 0 is clipped
				r_lastvertvalid = false;

			// we don't cache partially clipped edges
				cacheoffset = 0x7FFFFFFF;

				f = d0 / (d0 - d1);
				#{RCE_clipvert->p[0] = RCE_pv0->p[0] + f * (RCE_pv1->p[0] - RCE_pv0->p[0])};
				#{RCE_clipvert->p[1] = RCE_pv0->p[1] + f * (RCE_pv1->p[1] - RCE_pv0->p[1])};
				#{RCE_clipvert->p[2] = RCE_pv0->p[2] + f * (RCE_pv1->p[2] - RCE_pv0->p[2])};
				

				if (#{clip->leftedge} != 0)
				{
					r_leftclipped = true;
					VectorCopy(RCE_clipvert,#{r_leftenter.p}); 
				}
				else if (#{clip->rightedge} != 0)
				{
					r_rightclipped = true;
					VectorCopy(RCE_clipvert,#{r_rightenter.p});
				}
				
				RCE_swap = RCE_clipvert;
				RCE_clipvert = RCE_pv0;
				RCE_pv0 = RCE_swap;
				R_ClipEdge (#{clip->next});
				return;
			}
		} while ((#{clip = clip->next}) > 0);
	}

// add the edge
	
	R_EmitEdge (RCE_pv0, RCE_pv1);
}

/*
================
R_EmitEdge
================
*/
private function R_EmitEdge (pv0 : Int, pv1 : Int)
{
	#{s| vec3 *pv0, *pv1}
	#{d| edge_t	*edge, *pcheck}
	#{d| int		u_check}
	#{d| float	u, u_step}
	//vec3_t	local, transformed;
	#{d| float	*world}
	#{d| int		v, v2, ceilv0}
	#{d| float	scale, lzi0, u0, v0}
	var side : Bool;
	
	if (r_lastvertvalid)
	{
		u0 = r_u1;
		v0 = r_v1;
		lzi0 = r_lzi1;
		ceilv0 = r_ceilv1;
	}
	else
	{
		world = pv0;
		
	// transform and project
		VectorSubtract (world, #{r_origin}, #{REE_local.p});
		
		TransformVector (#{REE_local.p}, #{REE_transformed.p});
		
		if (#{REE_transformed.p[2]} < NEAR_CLIP)
			#{REE_transformed.p[2] = NEAR_CLIP};
		
		lzi0 = 1.0 / #{REE_transformed.p[2]};
		
	// FIXME: build x/yscale into transform?
		scale = xscale * lzi0;
		u0 = (xcenter + scale*#{REE_transformed.p[0]});
		if (u0 < ord_fvrectx_adj)
			u0 = ord_fvrectx_adj;
		if (u0 > ord_fvrectright_adj)
			u0 = ord_fvrectright_adj;
		
		scale = yscale * lzi0;
		v0 = (ycenter - scale*#{REE_transformed.p[1]});
		
		if (v0 < ord_fvrecty_adj)
			v0 = ord_fvrecty_adj;
		if (v0 > ord_fvrectbottom_adj)
			v0 = ord_fvrectbottom_adj;
		
		ceilv0 = Math.ceil(v0);
	}
	
	world = pv1;
	
// transform and project
	VectorSubtract (world, #{r_origin}, #{REE_local.p});
	TransformVector (#{REE_local.p}, #{REE_transformed.p});
	
	if (#{REE_transformed.p[2]} < NEAR_CLIP)
		#{REE_transformed.p[2] = NEAR_CLIP};
	
	r_lzi1 = 1.0 / #{REE_transformed.p[2]};
	
	scale = xscale * r_lzi1;
	r_u1 = (xcenter + scale*#{REE_transformed.p[0]});
	if (r_u1 < ord_fvrectx_adj)
		r_u1 = ord_fvrectx_adj;
	if (r_u1 > ord_fvrectright_adj)
		r_u1 = ord_fvrectright_adj;
	
	scale = yscale * r_lzi1;
	r_v1 = (ycenter - scale*#{REE_transformed.p[1]});
	if (r_v1 < ord_fvrecty_adj)
		r_v1 = ord_fvrecty_adj;
	if (r_v1 > ord_fvrectbottom_adj)
		r_v1 = ord_fvrectbottom_adj;
	
	if (r_lzi1 > lzi0)
		lzi0 = r_lzi1;
	
	if (lzi0 > r_nearzi)	// for mipmap finding
		r_nearzi = lzi0;
	
// for right edges, all we want is the effect on 1/z
	if (r_nearzionly) return;
	
	r_emitted = 1;
	
	r_ceilv1 = Math.ceil(r_v1);
	
	
// create the edge
	if (ceilv0 == r_ceilv1)
	{
	// we cache unclipped horizontal edges as fully clipped
		if (cacheoffset != 0x7FFFFFFF)
		{
			cacheoffset = FULLY_CLIPPED_CACHED | (r_framecount & FRAMECOUNT_MASK);
		}
		return;		// horizontal edge
	}
	
	side = ceilv0 > r_ceilv1;
	
	edge = edge_p;
	edge_p += #{sizeof(edge_t)};
	
	#{edge->prev = #[-1]#};
	#{edge->next = #[-1]#};
	#{edge->owner = r_pedge};
	#{edge->nearzi = lzi0};
	
	
	
	if (!side)
	{
	// trailing edge (go from p1 to p2)
		v = ceilv0;
		v2 = r_ceilv1 - 1;

		#{edge->surfs[0] = #[Std.int(]#(surface_p - surfaces)/sizeof(surf_t))};
		#{edge->surfs[1] = 0};

		u_step = ((r_u1 - u0) / (r_v1 - v0));
		u = u0 + (v - v0) * u_step;
	}
	else
	{
	// leading edge (go from p2 to p1)
		v2 = ceilv0 - 1;
		v = r_ceilv1;

		#{edge->surfs[0] = 0};
		#{edge->surfs[1] = #[Std.int(]#(surface_p - surfaces)/sizeof(surf_t))};

		u_step = ((u0 - r_u1) / (v0 - r_v1));
		u = r_u1 + (v - r_v1) * u_step;
	}
	
	
	#{edge->u_step = #[Std.int(]#u_step#[*0x100000]#)};
	u_check = Std.int(u*0x100000 + 0xFFFFF);
	
// we need to do this to avoid stepping off the edges if a very nearly
// horizontal edge is less than epsilon above a scan, and numeric error causes
// it to incorrectly extend to the scan, and the extension of the line goes off
// the edge of the screen
// FIXME: is this actually needed?
	if (u_check < ord_vrect_x_adj_shift20) {
		u_check = ord_vrect_x_adj_shift20;
	}
	if (u_check > ord_vrectright_adj_shift20) {
		u_check = ord_vrectright_adj_shift20;
	}
	
//
// sort the edge in normally
//
	#{edge->u = u_check};
	
	if (#{edge->surfs[0]} > 0)
		u_check++;	// sort trailers after leaders

	if (#{newedges[v]} < 0 || Memory.getI32(#{newedges[v]}+4) >= u_check)
	{
		#{edge->next = newedges[v]};
		#{newedges[v] = edge};
	}
	else
	{
		pcheck = #{newedges[v]};
		while (#{pcheck->next} > 4 && #{pcheck->next->u} < u_check)
			pcheck = #{pcheck->next};
		#{edge->next = pcheck->next};
		#{pcheck->next = edge};
	}
	
	#{edge->nextremove = removeedges[v2]};
	#{removeedges[v2] = edge};
	
}



